package com.hulk.dryad.admin.security.config;

import com.hulk.dryad.admin.security.handler.DryadLogoutSuccessHandler;
import com.hulk.dryad.admin.security.token.TokenAuthenticationFilter;
import com.hulk.dryad.admin.security.token.TokenLoginFilter;
import com.hulk.dryad.admin.security.token.TokenManager;
import com.hulk.dryad.common.constant.SecurityConstants;
import com.hulk.dryad.manage.security.component.DryadAccessDeniedHandler;
import com.hulk.dryad.manage.security.component.DryadPreAuthenticationChecks;
import com.hulk.dryad.manage.security.component.PermitAllSecurityUrlProperties;
import com.hulk.dryad.manage.security.handler.FormAuthenticationFailureHandler;
import com.hulk.dryad.manage.security.handler.TenantSavedRequestAwareAuthenticationSuccessHandler;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.firewall.StrictHttpFirewall;


/**
 * SpringSecurity配置
 *
 * @author hulk
 * @date 2020/3/18 10:54
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(PermitAllSecurityUrlProperties.class)
@RequiredArgsConstructor
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

	public final MessageSource messageSource;
	private final UserDetailsChecker dryadPreAuthenticationChecks = new DryadPreAuthenticationChecks();
	private final PermitAllSecurityUrlProperties permitAllSecurityUrlProperties;
	private final AuthenticationEventPublisher defaultAuthenticationEventPublisher;
	private final UserDetailsService adminSysUserDetailsService;
	private final AuthenticationEntryPoint dryadAuthExceptionEntryPoint;
	private final TokenManager adminTokenManager;


	@Override
	public void configure(WebSecurity web) {
		web.httpFirewall(new StrictHttpFirewall());
		web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**")
				.antMatchers(permitAllSecurityUrlProperties.getIgnoring().toArray(new String[0]));
	}

	@SneakyThrows
	@Override
	protected void configure(HttpSecurity http) {


		http.formLogin()
				.loginPage(SecurityConstants.LOGIN_PAGE)
				.loginProcessingUrl(SecurityConstants.LOGIN_PROCESSING_URL)
				.failureHandler(authenticationFailureHandler())
				.successHandler(tenantSavedRequestAwareAuthenticationSuccessHandler())
				.permitAll()
				.and()
				.logout()
				.disable();
//				.logoutUrl(SecurityConstants.LOGOUT_URL)
//				.clearAuthentication(false)
//				.logoutSuccessHandler(DryadLogoutSuccessHandler())
//				.deleteCookies("JSESSIONID")
//				.invalidateHttpSession(true)
//				.logoutSuccessUrl("/");


		//开启模拟请求，比如API POST测试工具的测试，不开启时，API POST为报403错误
		http.csrf()
				.disable()
				.headers()
				.frameOptions()
				.disable()
				.and()
				//全局不创建session
				.sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.STATELESS);

		//放开一些接口的权限校验
		ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
		permitAllSecurityUrlProperties.getPermitAll().forEach(url -> registry.antMatchers(url).permitAll());
		//其余的都需授权访问
		http.authorizeRequests()
				.anyRequest()
				.authenticated()
				.and()
				//未授权时访问须授权的资源端点
				.exceptionHandling()
				.authenticationEntryPoint(dryadAuthExceptionEntryPoint)
				.accessDeniedHandler(dryadAccessDeniedHandler());

		//关闭跨域访问
		http.cors().disable();

		// 权限filter
		http.addFilter(new TokenLoginFilter(authenticationManagerBean(), adminTokenManager, authenticationFailureHandler()));
		http.addFilter(new TokenAuthenticationFilter(authenticationManagerBean(), adminTokenManager, adminSysUserDetailsService));


	}

	@SneakyThrows
	@Override
	public void configure(AuthenticationManagerBuilder auth)  {
		auth.authenticationProvider(daoAuthenticationProvider());
		auth.authenticationEventPublisher(defaultAuthenticationEventPublisher);
		auth.eraseCredentials(false);
	}


	@Bean
	public DaoAuthenticationProvider daoAuthenticationProvider() {
		DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
		daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
		daoAuthenticationProvider.setUserDetailsService(adminSysUserDetailsService);
		daoAuthenticationProvider.setPostAuthenticationChecks(dryadPreAuthenticationChecks);
		daoAuthenticationProvider.setMessageSource(messageSource);
		//daoAuthenticationProvider.setUserDetailsPasswordService(); //密码更新
		return daoAuthenticationProvider;
	}


	/**
	 * 解决 无法直接注入 AuthenticationManager
	 *
	 */
	@Bean
	@Override
	@SneakyThrows
	public AuthenticationManager authenticationManagerBean()  {
		return super.authenticationManagerBean();
	}

	@Bean
	public AuthenticationFailureHandler authenticationFailureHandler() {
		return new FormAuthenticationFailureHandler();
	}
	/**
	 * 具备租户传递能力
	 * @return AuthenticationSuccessHandler
	 */
	@Bean
	public AuthenticationSuccessHandler tenantSavedRequestAwareAuthenticationSuccessHandler() {
		return new TenantSavedRequestAwareAuthenticationSuccessHandler();
	}

	@Bean
	public AccessDeniedHandler dryadAccessDeniedHandler (){
		return new DryadAccessDeniedHandler();
	}

//	@Bean
//	public LogoutSuccessHandler DryadLogoutSuccessHandler(){
//		return new DryadLogoutSuccessHandler();
//	}
	/**
	 * https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
	 * Encoded password does not look like BCrypt
	 * @return PasswordEncoder
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		return PasswordEncoderFactories.createDelegatingPasswordEncoder();
		//return new BCryptPasswordEncoder();
	}


}
